#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Round_Button.H>

#include <unistd.h>
#include <memory.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <stdint.h>

#include "codec2.h"

/*
  Codec2 demo

  Copyright (C) 2011,2012 Peter Lawrence

  All rights reserved.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License version 2.1, as
  published by the Free Software Foundation.  This program is
  distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>.
*/

#define WINDOW_X 200
#define WINDOW_Y 200
#define BORDER 8

/* local static function prototypes */
static void asyncevent_cb(int fd, void *arg);

/* static local variables */
static Fl_Window *win;
static Fl_Choice *choice;
static Fl_Round_Button *radio[3];
static Fl_Button *start, *stop;
static int active;
static struct CODEC2 *codec2_2500, *codec2_1400;
static unsigned char *c2_frame;
uint32_t audio_buffer_size;
static short *c2in, *c2out;

/*
TODO: override handler for Fl_Choice *choice to allow list of soundcards to updated each time the drop box is opened
this would allow someone to insert a USB soundcard into the PC and access it without having to re-run c2demo
*/

static const char *radio_captions[3] = { "uncompressed", "Codec2 2500bps", "Codec2 1400bps" };

static void radio_cb( Fl_Widget* o, void *arg )
{
	if ( (o == radio[0]) || (o == radio[1]) || (o == radio[2]) )
	{
		radio[0]->value(o == radio[0]);
		radio[1]->value(o == radio[1]);
		radio[2]->value(o == radio[2]);
	}
}

static void start_cb(Fl_Widget* o, void *arg)
{
	int fd, sndparam;

	fd = open(choice->text(), O_RDWR);

	if (fd > 0)
	{
		sndparam = AFMT_S16_LE;
		if (0 == ioctl(fd, SNDCTL_DSP_SETFMT, &sndparam))
		{
			sndparam = 0; /* 1 channel */
			if (0 == ioctl(fd, SNDCTL_DSP_STEREO, &sndparam))
			{
				sndparam = 8000;
				if (0 == ioctl(fd, SNDCTL_DSP_SPEED, &sndparam))
				{
					start->deactivate();
					stop->activate();
					choice->deactivate();

					active = 1;

					Fl::add_fd(fd, asyncevent_cb, NULL);
				}
			}
		}
	}

	if (!active)
		fl_alert("soundcard would not accept configuration; start aborted");
}

static void stop_cb(Fl_Widget* o, void *arg)
{
	active = 0;
}

static void populate_choice(Fl_Choice *c)
{
	DIR *dirp;
	struct dirent *dp;
	char caption[64];

	c->clear();

	dirp = opendir("/dev");

	while (dirp)
	{
		if (NULL == (dp = readdir(dirp)))
			break;
		if (0 == strncmp(dp->d_name, "dsp", 3))
		{
			snprintf(caption, sizeof(caption), "/dev/%s", dp->d_name);
			c->add(caption, 0, NULL, 0, 0);
		}
	}

	closedir(dirp);
}

int main (int argc, char *argv[])
{
	int pos, i;

	/* initialize instances of Codec2 (one for each bitrate) */
	codec2_2500 = codec2_create(CODEC2_MODE_2500);
	codec2_1400 = codec2_create(CODEC2_MODE_1400);

	if (NULL == codec2_2500)
	{
		fl_alert("Codec2 did not initialize; software terminating");
		return -1;
	}

	/* determine worst case Codec2 frame size */
	i = codec2_bits_per_frame(codec2_2500);
	if (codec2_bits_per_frame(codec2_1400) > i)
		i = codec2_bits_per_frame(codec2_1400);

	/* allocate memory for Codec2 frame size */
	c2_frame = (uint8_t *)malloc((i + 7) / 8);

	/* determine worst case Codec2 samples per frame */
	audio_buffer_size = codec2_samples_per_frame(codec2_2500);
	if (codec2_samples_per_frame(codec2_1400) > audio_buffer_size)
		audio_buffer_size = codec2_samples_per_frame(codec2_1400);

	/* scale value from units of samples to bytes */
	audio_buffer_size *= sizeof(short);

	c2in = (short *)malloc(audio_buffer_size);
	c2out = (short *)malloc(audio_buffer_size);

	if (!c2_frame || !c2in || !c2out)
	{
		fl_alert("buffer allocation failed; software terminating");
		return -1;
	}

	win = new Fl_Window(WINDOW_X, WINDOW_Y, "Codec2 demo");

	win->begin();

	pos = BORDER;
	for (i = 0; i < 3; i++)
	{
		radio[i] = new Fl_Round_Button(BORDER, pos, WINDOW_X - 2 * BORDER, 30, radio_captions[i]);
		radio[i]->callback(radio_cb, NULL);
		radio[i]->value(0 == i);
		pos += 30;
	}

	pos += 10;
	start = new Fl_Button(BORDER, pos, (WINDOW_X - 3 * BORDER) / 2, 30, "St&art");
	start->activate();
	start->callback(start_cb, NULL);
	stop = new Fl_Button(WINDOW_X - BORDER - (WINDOW_X - 3 * BORDER) / 2, pos, (WINDOW_X - 3 * BORDER) / 2, 30, "S&top");
	stop->deactivate();
	stop->callback(stop_cb, NULL);
	pos += 40;

	choice = new Fl_Choice(BORDER, pos, WINDOW_X - 2 * BORDER, 30);
	populate_choice(choice);
	if (choice->size())
	{
		choice->value(0);
	}
	else
	{
		fl_alert("no soundcard found; software terminating");
		return -1;
	}

	win->end();

	if (NULL == codec2_2500)
		radio[1]->deactivate();
	if (NULL == codec2_1400)
		radio[2]->deactivate();

	win->show();

	Fl::lock();

	/* hand the keys to FLTK; it does all the work now */
	i = Fl::run();

	codec2_destroy(codec2_2500);
	if (codec2_1400)
		codec2_destroy(codec2_1400);

	return i;
}

static void asyncevent_cb(int fd, void *arg)
{
	struct CODEC2 *codec2_context;
	uint32_t i;

	if (active)
	{
		read(fd, c2in, audio_buffer_size);

		if (radio[0]->value())
		{
			/* uncompressed pass through */
			memcpy(c2out, c2in, audio_buffer_size);
		}
		else
		{
			/* Codec2 is selected; pick between the two bit rates */
			if (radio[1]->value())
				codec2_context = codec2_2500;
			else
				codec2_context = codec2_1400;

			/* encode and then decode all the audio frames */
			for (i = 0; i < (audio_buffer_size / sizeof(short)); i += codec2_samples_per_frame(codec2_context))
			{
				codec2_encode(codec2_context, c2_frame, c2in + i);
				codec2_decode(codec2_context, c2out + i, c2_frame);
			}
		}

		write(fd, c2out, audio_buffer_size);
	}
	else
	{
		close(fd);

		start->activate();
		stop->deactivate();
		choice->activate();
	}
}

